<?php

namespace common\classes;

use Yii;
use yii\helpers\Json;
use common\models\YwSite;
use common\service\YsWeixin;
use common\models\YwWarning;

class YcWeixin extends Classes{
    private $_redis = null;
    private $_cachePrefix = null;
    public $cacheExpire = 7000;
    public $refreshFlag = false;
    public $refreshCode = [40001, 42001, 40014, 40030];
    public $warningCode = [50002, 48001, 48004];
    
    public function getRedis(){
        if($this->_redis === null){
            try{
                $this->_redis = Yii::$app->redis;
            }catch( Exception $e ) {
                Yii::error('redis not set'.print_r($e->getMessage(),true));
            }
        }
        return $this->_redis;
    }
    
    public function getCachePrefix(){
        $cachePrefix = 'weixin';
        if($this->_cachePrefix === null){
            $this->_cachePrefix = Yii::$app->id.'@'.$cachePrefix;
        }
        return $this->_cachePrefix;
    }
    
    public function refresh($flag = true){
        $this->refreshFlag = $flag;
        return $this;
    }
    
    /**
     * 防止重复获取对site加锁
     * @param type $siteId
     * @return type
     */
    public function getSiteForUpdate($siteId){
        $sql = 'SELECT * FROM '.YwSite::tableName().' WHERE id = :siteId FOR UPDATE';
        $params = [':siteId' => $siteId];
        return YwSite::findBySql($sql, $params)->one();
    }
    
    private function removeCache($cacheKey) {
		try {
			$res = $this->redis->del($cacheKey);
			Yii::info( "Remove {$cacheKey} from cache success!", __METHOD__ );
		}
		catch(Exception $e) {
			Yii::warning( "Remove {$cacheKey} from cache exception, " . $e->getMessage(), __METHOD__ );
		}
	}
    
    /**
     * 获取缓存中的accessToken
     * @param type $siteId
     * @return type
     */
    public function accessToken($siteId){
        $cacheKey1 = $this->cachePrefix.'@accesstoken@siteid:'.$siteId.':1';
        $cacheKey2 = $this->cachePrefix.'@accesstoken@siteid:'.$siteId.':2';
        if($this->refreshFlag){
            $this->removeCache($cacheKey1);
            $this->removeCache($cacheKey2);
        }
        
        try {
            $data = $this->redis->GET($cacheKey1); //取不到返回false
        }catch(Exception $e){
            Yii::error("Get {$cacheKey1} from cache1 error! " . $e->getMessage(),__METHOD__ );
            $data = null;
        }
        if(empty($data)){
            try {
                $data = $this->redis->GET($cacheKey2); //取不到返回false
            }catch(Exception $e){
                Yii::error("Get {$cacheKey2} from cache1 error! " . $e->getMessage(),__METHOD__ );
                $data = null;
            }
        }        
        if(empty($data)){
            $data = $this->getAccessToken($siteId);
            try {
                $res = $this->redis->SETEX($cacheKey1, $this->cacheExpire, $data);
                if($res) {
                    Yii::warning("accessToken {$cacheKey1} to cache", __METHOD__ );
                }else{
                    Yii::warning("accessToken {$cacheKey1} to cache error, code is".$res, __METHOD__ );
                }
            }catch(Exception $e) {
                Yii::error("accessToken {$cacheKey1} to cache error, exception is: " . $e->getMessage(), __METHOD__ );
            }
            
            try {
                $res2 = $this->redis->SETEX($cacheKey2, $this->cacheExpire+20, $data);
                if($res2) {
                    Yii::warning("accessToken {$cacheKey2} to cache", __METHOD__ );
                }else{
                    Yii::warning("accessToken {$cacheKey2} to cache error, code is".$res2, __METHOD__ );
                }
            }catch(Exception $e) {
                Yii::error("accessToken {$cacheKey2} to cache error, exception is: " . $e->getMessage(), __METHOD__ );
            }
        }
        $this->refreshFlag = false;
        $tokenArray = json_decode($data,true);
        return $tokenArray['access_token'];
    }
    
    /**
     * 获取mysql中的accessToken
     * @param type $siteId
     * @return type
     */
    public function getAccessToken($siteId){
        //Yii::error('getAccessToken cache miss:'.$siteId,__METHOD__);
        
        $siteInfo = YwSite::findOne($siteId);
        if(empty($siteInfo) || empty($siteInfo->params)){
            Yii::error('getAccessToken siteInfo null id:'.$siteId,__METHOD__);
            return false;
        }
        if($siteInfo->blockade == 0 && rand(0, 9) > 0){
            return false;
        }
        
        $siteParams = Json::decode($siteInfo->params,true);
        if(empty($siteParams['appId']) || empty($siteParams['secret'])){
            Yii::error('getAccessToken siteInfo appId/secret null id:'.$siteId,__METHOD__);
            return false;
        }
        
        //强制刷新accessToken
        if($this->refreshFlag){
            Yii::error('getAccessToken cache refresh:'.$siteId,__METHOD__);
            return $this->refreshAccessToken($siteInfo->id);
        }
        
        if(empty($siteInfo->access_token)){
            return $this->refreshAccessToken($siteInfo->id);
        }
        
        $tokenArray = Json::decode($siteInfo->access_token,true);
        if(empty($tokenArray) || empty($tokenArray['access_token']) || $tokenArray['up_time'] < time()-7000){
            return $this->refreshAccessToken($siteInfo->id);
        }
        return $siteInfo->access_token;
    }
    
    /**
     * 刷新weixin access_token
     * @return bool
     */
    public function refreshAccessToken($siteId){
        Yii::error('getAccessToken data miss:'.$siteId,__METHOD__);
        
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $siteInfo = $this->getSiteForUpdate($siteId);
            if(empty($siteInfo) || empty($siteInfo->params)){
                Yii::error('refreshAccessToken siteInfo null id:'.$siteId,__METHOD__);
                $transaction->rollBack();
                return false;
            }
            $siteParams = Json::decode($siteInfo->params,true);
            if(empty($siteParams['appId']) || empty($siteParams['secret'])){
                Yii::error('refreshAccessToken siteInfo appId/secret null id:'.$siteId,__METHOD__);
                $transaction->rollBack();
                return false;
            }
            if(!empty($siteInfo->access_token)){
                $tokenArray = Json::decode($siteInfo->access_token,true);
                if(!empty($tokenArray) && !empty($tokenArray['access_token']) && $tokenArray['up_time'] >= time()-10){
                    $transaction->rollBack();
                    return $tokenArray;
                }
            }
            
            $tokenUrl = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$siteParams['appId'].'&secret='.$siteParams['secret'];
            $tokenJson = YsWeixin::service()->get_by_curl($tokenUrl);
            $tokenArray = Json::decode($tokenJson,true);
            if(isset($tokenArray['access_token']) == false || empty($tokenArray['access_token'])){
                Yii::error('refreshAccessToken tokenUrl:'.$tokenUrl.' error:'.print_r($tokenArray,true),__METHOD__);
                $transaction->rollBack();
                
                if(isset($tokenArray['errcode']) && in_array($tokenArray['errcode'], $this->warningCode) && $siteInfo->blockade == 10){
                    $warning = 'errcode:'.$tokenArray['errcode'].' errmsg:'.$tokenArray['errmsg'];
                    $this->warningWeixin($siteId,$warning);
                }
                return false;
            }
            $accessTokenArray = [
                'up_time'=>time(),
                'access_token'=>$tokenArray['access_token'],
            ];
            $accessTokenJson = Json::encode($accessTokenArray);
            $siteInfo->access_token = $accessTokenJson;
            if($siteInfo->save() == false){
                Yii::error('refreshAccessToken siteInfo save false attr:'.print_r($siteInfo->attributes,true).' error:'.print_r($siteInfo->errors,true));
                $transaction->rollBack();
                return false;
            }
            $transaction->commit();
        } catch (\Exception $ex) {
            Yii::error('refreshAccessToken Exception siteId:'.$siteId.' Exception:'.print_r($ex,true));
            $transaction->rollBack();
            return false;
        }
        return $accessTokenJson;
    }
    
    
    public function jsapiTicket($siteId){
        if($this->refreshFlag){
            $jsapiTicketArray = $this->cache(7000)->refresh()->getJsapiTicketFromCache($siteId);
        }else{
            $jsapiTicketArray = $this->cache(7000)->getJsapiTicketFromCache($siteId);
        }
         
        if(empty($jsapiTicketArray['jsapi_ticket']) || $jsapiTicketArray['up_time'] < time() - 7000){
            $jsapiTicketArray = $this->cache(7000)->refresh()->getJsapiTicketFromCache($siteId);
        }
        $this->refreshFlag = false;
        return $jsapiTicketArray['jsapi_ticket'];
    }
    
    /**
     * 获取mysql中的accessToken
     * @param type $siteId
     * @return type
     */
    public function getJsapiTicket($siteId){
        Yii::error('getJsapiTicket cache miss:'.$siteId,__METHOD__);
        
        $siteInfo = YwSite::findOne($siteId);
        if(empty($siteInfo) || empty($siteInfo->params)){
            Yii::error('getJsapiTicket siteInfo null id:'.$siteId,__METHOD__);
            return false;
        }
        
        //强制刷新accessToken
        if($this->refreshFlag){
            Yii::error('getJsapiTicket cache refresh:'.$siteId,__METHOD__);
            return $this->refreshJsapiTicket($siteInfo->id);
        }
        
        if(empty($siteInfo->jsapi_ticket)){
            return $this->refreshJsapiTicket($siteInfo->id);
        }
        
        $jsapiArray = Json::decode($siteInfo->jsapi_ticket,true);
        if(empty($jsapiArray) || empty($jsapiArray['jsapi_ticket']) || $jsapiArray['up_time'] < time()-7000){
            return $this->refreshJsapiTicket($siteInfo->id);
        }
        return $jsapiArray;
    }
    
    /**
     * 刷新weixin Jsapi Ticket
     * @return bool
     */
    public function refreshJsapiTicket($siteId){
        Yii::error('getJsapiTicket data miss:'.$siteId,__METHOD__);
        
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $siteInfo = $this->getSiteForUpdate($siteId);
            if(empty($siteInfo) || empty($siteInfo->params)){
                Yii::error('getJsapiTicket siteInfo null id:'.$siteId,__METHOD__);
                $transaction->rollBack();
                return false;
            }
            if(!empty($siteInfo->jsapi_ticket)){
                $jsapiArray = Json::decode($siteInfo->jsapi_ticket,true);
                if(!empty($jsapiArray) && !empty($jsapiArray['jsapi_ticket']) && $jsapiArray['up_time'] >= time()-10){
                    $transaction->rollBack();
                    return $jsapiArray;
                }
            }
            
            $this->refreshFlag = false;
            $ticketUrl = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='.$this->accessToken($siteInfo->id).'&type=jsapi';
            $ticketJson = YsWeixin::service()->get_by_curl($ticketUrl);
            $ticketArray = Json::decode($ticketJson,true);
            if(isset($ticketArray['ticket']) == false || empty($ticketArray['ticket'])){
                if(isset($ticketArray['errcode']) && in_array($ticketArray['errcode'], $this->refreshCode)){
                    $this->refresh()->accessToken($siteInfo->id);
                }
                Yii::error('refreshJsapiTicket ticketUrl:'.$ticketUrl.' error:'.print_r($ticketArray,true),__METHOD__);
                $transaction->rollBack();
                
                if(isset($ticketArray['errcode']) && in_array($ticketArray['errcode'], $this->warningCode)){
                    $warning = 'errcode:'.$ticketArray['errcode'].' errmsg:'.$ticketArray['errmsg'];
                    $this->warningWeixin($siteInfo->id,$warning);
                }
                return false;
            }
            $jsapiTicketArray = [
                'up_time'=>time(),
                'jsapi_ticket'=>$ticketArray['ticket'],
            ];
            $jsapiTicketJson = Json::encode($jsapiTicketArray);
            $siteInfo->jsapi_ticket = $jsapiTicketJson;
            if($siteInfo->save() == false){
                Yii::error('refreshJsapiTicket siteInfo save false attr:'.print_r($siteInfo->attributes,true).' error:'.print_r($siteInfo->errors,true));
                $transaction->rollBack();
                return false;
            }
            $transaction->commit();
        } catch (Exception $ex) {
            Yii::error('refreshJsapiTicket Exception siteId:'.$siteId.' Exception:'.print_r($ex,true));
            $transaction->rollBack();
            return false;
        }
        return $jsapiTicketArray;
    }
    
    /**
     * 出现封号异常,报警
     * @param type $siteId
     */
    public function warningWeixin($siteId,$warning){
        $siteInfo = YwSite::findOne($siteId);
        if($siteInfo->blockade == 0){
            return false;
        }
        
        $warningCount = YwWarning::find()->where(['site_id'=>$siteId,'type'=>1])->andWhere(['>','created_at',(time()-1800)])->count(1);
        if($warningCount > 0){
            return false;
        }
        
        $mobile = '18581991172,18610212550,13908191475,18200197530';
        $email = '165516257@qq.com,524606@qq.com,930110634@qq.com,469061711@163.com';
        $serverIp = isset(Yii::$app->params['serverIp'])?Yii::$app->params['serverIp']:'';
        
        $ywWarning = new YwWarning;
        $ywWarning->type = 1;
        $ywWarning->site_id = $siteId;
        $ywWarning->warning = $warning;
        $ywWarning->status = 0;
        $ywWarning->mobile = $mobile;
        $ywWarning->email = $email;
        $ywWarning->ip = $serverIp;
        if($ywWarning->save() == false){
            Yii::error('warningWeixin YwWarning save false attr:'.print_r($ywWarning->attributes,true).' error:'.print_r($ywWarning->errors,true));
            return false;
        }
        return true;
    }
}
